package com.starleague.battletech.controller;

import java.io.IOException;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.starleague.StarLeagueException;
import com.starleague.battletech.command.AbstractBattletechCommand;
import com.starleague.battletech.game.dto.IBattletechGameDto;
import com.starleague.battletech.rules.initiative.InitiativePhaseState;
import com.starleague.battletech.rules.movement.MovementPhaseState;
import com.starleague.battletech.unit.mech.MechMovementActionType;
import com.starleague.battletech.unit.mech.command.AbstractMechCommand;
import com.starleague.battletech.unit.mech.controller.IMechController;
import com.starleague.battletech.unit.mech.dto.IMechDto;
import com.starleague.hexagonal.IHexCoordinate;
import com.starleague.hexagonal.flat.FlatHexDirection;

public abstract class AbstractServerBattletechController<CONTEXT extends IBattletechContext>
        implements IBattletechGameController, IMechController
{
    private static final Logger log = LoggerFactory.getLogger(AbstractServerBattletechController.class);

    protected final IBattletechGameDto gameDto;
    protected final InitiativePhaseState initiativePhaseState;
    protected final MovementPhaseState movementPhaseState;

    // private Queue<BattletechGameAction> actionQueue = new LinkedList<BattletechGameAction>();

    // TODO: Game might have a current Mech.
    // If it's movement/action was interrupted 1/2 way through,
    // and still can perform more actions

    public AbstractServerBattletechController(IBattletechGameDto gameDto,
                                              InitiativePhaseState initiativePhaseState,
                                              MovementPhaseState movementPhaseState)
    {
        this.gameDto = gameDto;
        this.initiativePhaseState = initiativePhaseState;
        this.movementPhaseState = movementPhaseState;
    }

    @Override
    public void startGame()
    {
        execute(new AbstractBattletechCommand()
        {
            @Override
            public <COMMAND_CONTEXT extends IBattletechContext> void execute(COMMAND_CONTEXT context)
                    throws StarLeagueException
            {
                AbstractServerBattletechController.this.initiativePhaseState.startPhase(getGame(context));
            }

            @Override
            public String toString()
            {
                return super.toString() + " [StartGame]";
            }
        });
    }

    @Override
    public void mechWalk(final IMechDto mechDto,
                         final List<MechMovementActionType> movementActionTypeList) throws StarLeagueException
    {
        execute(new AbstractMechCommand(mechDto)
        {
            @Override
            public <COMMAND_CONTEXT extends IBattletechContext> void execute(COMMAND_CONTEXT context)
                    throws StarLeagueException
            {
                AbstractServerBattletechController.this.movementPhaseState.mechWalk(getGame(context),
                                                                                    getMech(context),
                                                                                    movementActionTypeList);
            }

            @Override
            public String toString()
            {
                return super.toString() + " [mechWalk]";
            }
        });
    }

    @Override
    public void mechRun(final IMechDto mechDto,
                        final List<MechMovementActionType> movementActionTypeList) throws StarLeagueException
    {
        execute(new AbstractMechCommand(mechDto)
        {
            @Override
            public <COMMAND_CONTEXT extends IBattletechContext> void execute(COMMAND_CONTEXT context)
                    throws StarLeagueException
            {
                AbstractServerBattletechController.this.movementPhaseState.mechRun(getGame(context),
                                                                                   getMech(context),
                                                                                   movementActionTypeList);
            }

            @Override
            public String toString()
            {
                return super.toString() + " [mechWalk]";
            }
        });
    }

    @Override
    public void mechStandStill(final IMechDto mechDto) throws StarLeagueException
    {
        execute(new AbstractMechCommand(mechDto)
        {
            @Override
            public <COMMAND_CONTEXT extends IBattletechContext> void execute(COMMAND_CONTEXT context)
                    throws StarLeagueException
            {
                AbstractServerBattletechController.this.movementPhaseState.mechStandStill(getGame(context),
                                                                                          getMech(context));
            }

            @Override
            public String toString()
            {
                return super.toString() + " [mechWalk]";
            }
        });
    }

    @Override
    public void mechJump(final IMechDto mechDto,
                         final IHexCoordinate hexCoordinate,
                         final FlatHexDirection facing) throws StarLeagueException
    {
        //
    }

    // @Override
    // public void moveMech(final IMechDto mechDto,
    // final MechMovementMode movementMode,
    // final List<MechMovementActionType> movementActionTypeList) throws StarLeagueException
    // {
    // execute(new AbstractMechCommand(mechDto)
    // {
    // @Override
    // public <COMMAND_CONTEXT extends IBattletechContext> void execute(COMMAND_CONTEXT context)
    // throws StarLeagueException
    // {
    // AbstractServerBattletechController.this.movementPhaseState.moveMech(getGame(context),
    // getMech(context),
    // movementMode,
    // movementActionTypeList);
    // }
    //
    // @Override
    // public String toString()
    // {
    // return super.toString() + " [MoveMech]";
    // }
    // });
    // }

    protected abstract CONTEXT createContext();

    // TODO: Make private, and feed off a Queue, generated from requests
    // TODO: How do we force actions to be performed one-at-a-time?
    // Can't just use locks, unless we restrict the server to a single instance.
    // IE: How do we make sure (serial) actions are not conflicting when we have a distributed system?
    // Table-lock on Game State?
    // TODO: SEDA Queues for actions (locally) for now?
    private void execute(AbstractBattletechCommand command)
    {
        // TODO: For Android, we need to use old-school resource management
        CONTEXT context = createContext();
        try
        {
            command.execute(context);
            context.onSuccess(command);
        }
        // TODO: Handle Specific exceptions related to correctness, state(phase), security, CommandIsInvalid, etc...
        catch (final Exception e)
        {
            log.error("Failed to execute: {}", command);
            log.error("Failure reason: ", e);
            context.onFailure(command, e.getMessage());
        }
        finally
        {
            try
            {
                context.close();
            }
            catch (IOException e)
            {
                log.error("Failure in context.close(): ", e);
            }
        }
    }
}

// public void invalid(AbstractBattletechGameCommand gameCommand)
// {
// log.warn("Invalid State [{}] for: {}", this.getClass().getSimpleName(), gameCommand);
// }

// TODO: Handle Requests directly, but some of them will become actions/commands with asynchronous results

//
// Handle all Requests here
// TODO: Make these methods into Request/Message objects (and handle them in the request)?
//

// public IMech getMech(IMech mech)
// {
// // TODO: Instead of querying the game, make a DB call to get the data(?)
// for (IBattletechUnit unit)
// return game.getUnits()
// private final IMech mech;
// private final List<MechMovementActionTypeType> movementActionTypeList;
// }

// public void moveMech(IMech mech, List<MechMovementActionTypeType> mechMovementActionTypeList)
// {
// MechMovementActionType mechMovementAction = new BattletechGameActionMechMovement()
// private final IMech mech;
// private final List<MechMovementActionTypeType> movementActionTypeList;
//
//
// }

// TODO: How do we force actions to be performed one-at-a-time?
// Can't just use locks, unless we restrict the server to a single instance.
// IE: How do we make sure (serial) actions are not conflicting when we have a distributed system?
// Table-lock on Game State?
// TODO: SEDA Queues for actions (locally) for now?
// public void handleRequest(BattletechRequest gameRequest)
// {
// this.gameState.perform(gameRequest);
// }

// }

// TODO: Steps for a Request:
// 1) Queue it
// 2) Determine if it is a setter or a getter
// **** Setter ****
// 3) Start a Transaction
// 4) Add Audit for the request
// 5) Execute the Request
// 6) Determine what changed, and create a change-record for all changes
// Think LiquiBase change-set, with a version# ??
// 7) Update Audit with status
// 8) End Transaction <= Succeed or fail, this is the PointOfNoReturn
// 9) Send response to Caller with Success or Failure message (and new Version # maybe?)
// 10) Send a message to all clients that Game Version has changed.

// private GaePersistManager persistManager;
// private IBattletechGame game;
//
// public BattletechGameController(IBattletechGame game)
// {
// // this.persistManager = persistManager;
// // this.gameId = gameId;
// }
// //
// protected GaeTransaction startTransaction()
// {
// return this.persistManager.startTransaction();
// }
//
// public BattletechGameController(GaePersistManager persistManager,
// Key gameId)
// {
// this.persistManager = persistManager;
// this.gameId = gameId;
// }
//
// protected GaeTransaction startTransaction()
// {
// return this.persistManager.startTransaction();
// }
//
// protected BattletechGameGae getGame(GaeTransaction transaction)
// {
// return transaction.find(BattletechGameGae.class, this.gameId);
// }

// TODO: Response/ResultEvents with affected entities

// BattletechTurnPhase getTurnPhase()
// {
// return this.game.getTurnPhase();
// }

//
// public Collection<? extends IBattletechTeam> getTeams()
// {
// return game.getTeams();
// }
//
// public IBattletechTeam getTeam(String name)
// {
// for (IBattletechTeam team : this.game.getTeams())
// if (team.getName().equalsIgnoreCase(name))
// return team;
// return null;
// }
//
// public IBattletechTeam getTeam(ID id)
// {
//
// }

// String getName();
//
//
// IBattletechHexGrid getHexGrid();
//
// Collection<? extends IBattletechUnit> getUnits();
//
// int getTurn();
//
// BattletechTurnPhase getTurnPhase();

